Option Explicit

'################################################################
' Descriptor
' 基础类型数据的数组引用
'################################################################

Implements IPrimitiveArrayRef

'################################################################
' Field
'################################################################
' 数组缓存
' 下标从 0 开始
Private data() As Variant

' 数组的最大索引
Private dataMaxIndex As Long

' 已经使用的最大索引
' 从 -1 开始，每次向末尾添加数据时需要检查该索引
' 如果索引大于等于 dataMaxIndex 则需要进行扩容
' 否则将数据直接写入 usedMaxIndex + 1 的位置
Private usedMaxIndex As Long

'################################################################
' Constructor
'################################################################
Private Sub Class_Initialize()
    dataMaxIndex = -1
    usedMaxIndex = -1
End Sub

Private Sub Class_Terminate()
End Sub

'################################################################
' Init
'################################################################
' @param initSize 数组初始化长度
'             必须提供一个 Integer 或 Long 型的、大于等于 0 的整数
Public Sub Init(Optional ByRef initSize As Variant = 10)
On Error Goto castError
    ' 1. 尝试将参数 initSize 转换成整数，如果失败则手动抛出异常
    dataMaxIndex = Int(initSize)
On Error Goto rangeError
    If dataMaxIndex < 0 Then
        Err.Raise 10009, Description:="Integer Or Long parameter[initSize]'s value must not be greater than or equal to 0. Now parameter[initSize] is " & initSize
        Exit Sub
    End If

    ' 2. 初始化参数
    dataMaxIndex = initSize - 1
    usedMaxIndex = -1

On Error Goto boundError
    ReDim data(0 To dataMaxIndex)

    Exit Sub
castError:
    Err.Clear
    Err.Raise 10008, Description:="parameter[initSize]'s type must not be Integer or Long. Now parameter[initSize] is " & initSize
    Exit Sub
boundError:
    Err.Clear
    Resume Next
rangeError:
    Err.Raise Err.Number, Err.Source, Err.Description
End Sub

' 克隆操作初始化
' 外部禁止使用
Public Sub InitUnsafeForClone(ByRef dataVal As Variant, ByRef dataMaxIndexVal As Long, ByRef usedMaxIndexVal As Long)
    data = dataVal
    dataMaxIndex = dataMaxIndexVal
    usedMaxIndex = usedMaxIndexVal
End Sub

'################################################################
' Property
'################################################################
' 获取当前数组引用中已使用的长度
Public Property Get IPrimitiveArrayRef_Count() As Long
    IPrimitiveArrayRef_Count = usedMaxIndex + 1
End Property

' 获取当前数组引用的最大索引
Public Property Get IPrimitiveArrayRef_MaxIndex() As Long
    IPrimitiveArrayRef_MaxIndex = usedMaxIndex
End Property

'################################################################
' Function
'################################################################
' 添加 count 个空数据到数组中
' 如果容量不足，将会自动扩容
' 空数据将会同一被设置成 Empty
'
' @param count 需要添加的空数据数量
Public Sub IPrimitiveArrayRef_AddNull(ByRef count As Long)
    ' 1. 计算新的最大索引
    Dim newUsedMaxIndex As Long
    newUsedMaxIndex = usedMaxIndex + count

    ' 2. 检查数组容量是否充足
    If newUsedMaxIndex > dataMaxIndex Then
        dataMaxIndex = newUsedMaxIndex + 10
        ReDim Preserve data(0 To dataMaxIndex)
    End If

    ' 3. 将新增部分的数据全部设置为 Empty
    Dim i As Long
    For i = (usedMaxIndex + 1) To newUsedMaxIndex
        data(i) = Empty
    Next

    ' 4. 重设最大索引
    usedMaxIndex = newUsedMaxIndex
End Sub

' 获取指定索引下的数据
' @param index 数组索引，如果越界将会抛出异常
Public Function IPrimitiveArrayRef_Item(ByRef index As Long) As Variant
    ' 1. 检查参数
    If index < 0 Then
        Err.Raise 10010, Description:="parameter[index]=" & index & " out of bounds. min index=0, max index[" & usedMaxIndex & "]"
        Exit Function
    End If
    If index > usedMaxIndex Then
        Err.Raise 10010, Description:="parameter[index]=" & index & " out of bounds. min index=0, max index[" & usedMaxIndex & "]"
        Exit Function
    End If

    ' 2. 返回数据
    IPrimitiveArrayRef_Item = data(index)
End Function

' 获取指定索引下的数据
' 获取数据时，将不会检查索引的范围，如果索引越界会产生异常
' 在可以保证索引一定安全的情况下，可以通过该方法来获取数据，通过省略索引范围检查来提升效率
' @param index 数组索引，如果越界将会抛出异常
Public Function IPrimitiveArrayRef_unsafeItem(ByRef index As Long) As Variant
    IPrimitiveArrayRef_unsafeItem = data(index)
End Function

' 设置指定索引下的数据
' @param index 数组索引，如果越界将会抛出异常
' @param val 需要保存到数组中的数据
Public Sub IPrimitiveArrayRef_setVal(ByRef index As Long, ByRef val As Variant)
    ' 1. 检查参数
    If index < 0 Then
        Err.Raise 10010, Description:="PrimitiveArrayRef.IObjArrayRef_setVal: parameter[index]=" & index & " out of bounds. min index=0, max index[" & usedMaxIndex & "]"
        Exit Sub
    End If
    If index > usedMaxIndex Then
        Err.Raise 10010, Description:="PrimitiveArrayRef.IObjArrayRef_setVal: parameter[index]=" & index & " out of bounds. min index=0, max index[" & usedMaxIndex & "]"
        Exit Sub
    End If

    If IsObject(val) Then
        Err.Raise 10004, Description:="PrimitiveArrayRef.IObjArrayRef_setVal: parameter val must be not Object or Nothing"
        Exit Sub
    End If

    ' 2. 设置数据
    data(index) = val
End Sub

' 向数组末尾添加数据，如果数组容量不足将会自动扩容
' @param val 需要保存到数组中的数据
Public Sub IPrimitiveArrayRef_Add(ByRef val As Variant)
    ' 1. 检查参数
    If IsObject(val) Then
        Err.Raise 10004, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_Add: parameter val must be not Object or Nothing"
        Exit Sub
    End If

    ' 2. 如果容量不足则进行扩容
    If usedMaxIndex >= dataMaxIndex Then
        If dataMaxIndex <= 0 Then
            dataMaxIndex = 1
        Else
            dataMaxIndex = CLng(dataMaxIndex * 1.5)
        End If
        ReDim Preserve data(0 To dataMaxIndex)
    End If

    ' 3. 索引自增后，保存数据
    usedMaxIndex = usedMaxIndex + 1
    data(usedMaxIndex) = val
End Sub

' 从数组的开头添加数据，如果数组容量不足将会自动扩容
' @param val 需要保存到数组中的数据
Public Sub IPrimitiveArrayRef_AddFirst(ByRef val As Variant)
    ' 1. 检查参数
    If IsObject(val) Then
        Err.Raise 10004, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_AddFirst: parameter val must be not Object or Nothing"
        Exit Sub
    End If

    ' 2. 如果容量不足则进行扩容
    If usedMaxIndex >= dataMaxIndex Then
        If dataMaxIndex <= 0 Then
            dataMaxIndex = 1
        Else
            dataMaxIndex = CLng(dataMaxIndex * 1.5)
        End If
        ReDim Preserve data(0 To dataMaxIndex)
    End If

    ' 3. 数组内的内容整体向后移动 1 位
    Dim i As Long
    For i = usedMaxIndex To 0 Step -1
        data(i + 1) = data(i)
    Next

    ' 4. 将新增数据保存到索引 0 处
    data(0) = val
    usedMaxIndex = usedMaxIndex + 1
End Sub

' 将数据 `val` 插入索引 `index` 的位置
' 支持的索引范围为 `0--IPrimitiveArrayRef.Count()`
'
' @param index 数据需要插入的索引
' @param val 需要保存到数组中的**对象类型数据**
Public Sub IPrimitiveArrayRef_Insert(ByRef index As Long, ByRef val As Variant)
    ' 1. 检查参数
    ' 1.1 检查 index 的范围
    If usedMaxIndex = -1 Then
        ' - 如果 usedMaxIndex == -1 --> 当前数组引用中还没有数据
        '     - 如果 index != 0，则抛出异常
        If index <> 0 Then
            Err.Raise 10010, Description:="IndexOutOfBounds: [index]=" & index & " out of bounds. count=0]"
            Exit Sub
        End If
    Else
        ' - 否则
        '     - 如果 index > (usedMaxIndex + 1) || index < 0
        If index < 0 Then
            Err.Raise 10010, Description:="IndexOutOfBounds: [index]=" & index & " out of bounds. count=" & (usedMaxIndex + 1) & "]"
            Exit Sub
        End If
        If index > (usedMaxIndex + 1) Then
            Err.Raise 10010, Description:="IndexOutOfBounds: [index]=" & index & " out of bounds. count=" & (usedMaxIndex + 1) & "]"
            Exit Sub
        End If
    End If

    ' 1.2 检查 val 的数据类型
    If IsObject(val) Then
        Err.Raise 10004, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_AddFirst: parameter val must be not Object or Nothing"
        Exit Sub
    End If

    ' 2. 如果容量不足则进行扩容
    If usedMaxIndex >= dataMaxIndex Then
        If dataMaxIndex <= 0 Then
            dataMaxIndex = 1
        Else
            dataMaxIndex = CLng(dataMaxIndex * 1.5)
        End If
        ReDim Preserve data(0 To dataMaxIndex)
    End If

    ' 3. 将从 MaxIndex 开始到 index 的所有数据向后移动一位
    Dim i As Long
    For i = usedMaxIndex To index Step -1
        data(i + 1) = data(i)
    Next

    ' 4. 将新增数据保存到索引 0 处
    data(index) = val
    usedMaxIndex = usedMaxIndex + 1
End Sub

' 清空数组内容
'
' @optional-param initSize 可选参数, 清空后重新初始化数组的容量
'                 小于 0 时，只会清除数组引用，不会调整数组容量
Public Sub IPrimitiveArrayRef_clear(Optional ByRef initSize As Long = -1)
    If initSize < 0 Then
        ' 只清除数组中的数据引用

        ' 如果当前数组还没有添加任何数据，则不需要执行 clear
        If usedMaxIndex < 0 Then
            Exit Sub
        End If

        Dim i As Long
        For i = 0 To usedMaxIndex
            data(i) = Empty
        Next
        usedMaxIndex = -1
    Else
        ' 调整数组长度
        dataMaxIndex = initSize - 1
        ReDim data(0 To dataMaxIndex)
        usedMaxIndex = -1
    End If
End Sub

' 删除指定索引下的数据
'
' 如果索引超出了数组的边界，不会抛出异常，默认不会执行任何操作
' 该操作会使数组的可用长度 - 1
'
' @param index 数组索引
Public Sub IPrimitiveArrayRef_remove(ByRef index As Long)
    ' 1. 检查参数
    If index < 0 Then
        Exit Sub
    End If

    If index > usedMaxIndex Then
        Exit Sub
    End If

    ' 2. 移动 index + 1 --> usedMaxIndex 的所有数据
    Dim i As Long
    For i = index To (usedMaxIndex - 1)
        data(i) = data(i + 1)
    Next

    ' 3. 移动数据后，删除最后一位的数据
    data(usedMaxIndex) = Empty

    ' 4. 可用长度 - 1
    usedMaxIndex = usedMaxIndex - 1
End Sub

' 尝试从当前数组中搜索与参数 val 相同的数据，并删除
'
' 如果 needAll=False, 则只会删除第一个与 val 相同的数据
' 如果 needAll=True, 则会遍历整个数组并删除与 val 相同的所有数据
' 该操作会使数组的可用长度变小
'
' @param val, 需要删除的数据
' @optinal-param needAll 是否需要删除所有相同的数据
'                默认=False，只删除第一个和 val 相同的数据
Public Sub IPrimitiveArrayRef_removeVal(ByRef val As Variant, Optional ByRef needAll As Boolean = False)
    ' 1. 检查参数
    If IsObject(val) Then
        ' 如果待删除的数据是一个对象类型，则立刻停止
        Exit Sub
    End If

    ' 2. 按照不同的删除策略来执行删除
    Dim i As Long
    If needAll = False Then
        ' 2.1 搜索第一个和 val 相同的数据
        For i = 0 To usedMaxIndex
            ' 如果相同，则移动数据
            If data(i) = val Then
                Exit For
            End If
        Next

        ' 如果成功所有到了和 val 相同的数据，则执行删除操作
        If i <= usedMaxIndex Then
            ' 删除并移动数据
            For i = i To (usedMaxIndex - 1)
                data(i) = data(i + 1)
            Next

            ' 移动数据后，删除最后一位的数据预编译不过
            data(usedMaxIndex) = Empty

            ' 可用长度 - 1
            usedMaxIndex = usedMaxIndex - 1
        End If
    Else
        ' 2.2 搜索并删除所有和 val 相同的数据
        ' 记录删除之后的数组长度
        Dim newUsedMaxIndex As Long
        newUsedMaxIndex = usedMaxIndex

        Dim j As Long
        j = -1
        For i = 0 To usedMaxIndex
            If data(i) = val Then
                newUsedMaxIndex = newUsedMaxIndex - 1
            Else
                j = j + 1

                If j <> i Then
                    data(j) = data(i)
                End If
            End If
        Next

        ' 将 newUsedMaxIndex + 1 --> usedMaxIndex 的所有数据引用清空
        For i = (newUsedMaxIndex + 1) To usedMaxIndex
            data(i) = Empty
        Next

        ' 调整可用长度
        usedMaxIndex = newUsedMaxIndex
    End If
End Sub

' 删除最后一个元素
Public Sub IPrimitiveArrayRef_removeLast()
    ' 1. 检查索引
    If usedMaxIndex <= -1 Then
        Exit Sub
    End If

    ' 2. 删除最后一位的数据
    data(usedMaxIndex) = Empty

    ' 3. 可用长度 - 1
    usedMaxIndex = usedMaxIndex - 1
End Sub

' 删除最后一个元素，并返回删除的数据
'
' @return 被删除的最后一个元素
'         如果当前数组引用中已经没有元素了，将会返回一个 `Empty`
Public Function IPrimitiveArrayRef_pollLast() As Variant
    ' 1. 检查索引
    If usedMaxIndex <= -1 Then
        IPrimitiveArrayRef_pollLast = Empty
        Exit Function
    End If

    ' 2. 获取最后一个索引的数据
    IPrimitiveArrayRef_pollLast = data(usedMaxIndex)

    ' 3. 删除最后一位的数据
    data(usedMaxIndex) = Empty

    ' 4. 可用长度 - 1
    usedMaxIndex = usedMaxIndex - 1
End Function

' 获取数组引用中的最后一个元素
' 如果当前数组引用是空的，则返回 `Empty`
Public Function IPrimitiveArrayRef_getLast() As Variant
    If usedMaxIndex = -1 Then
        IPrimitiveArrayRef_getLast = Empty
    Else
        IPrimitiveArrayRef_getLast = data(usedMaxIndex)
    End If
End Function

' 合并另一个基础类型数组引用中的数据
' @param others, 另一个基础类型数组引用
Public Sub IPrimitiveArrayRef_mergeArrRef(ByRef others As IPrimitiveArrayRef)
    ' 1. 检验参数
    If others Is Nothing Then
        Exit Sub
    End If

    Dim othersMaxIndex As Long
    othersMaxIndex = others.MaxIndex
    If othersMaxIndex = -1 Then
        Exit Sub
    End If

    ' 2. 检查数组容量是否充足
    Dim newUsedMaxIndex As Long
    newUsedMaxIndex = othersMaxIndex + usedMaxIndex + 1
    If newUsedMaxIndex > dataMaxIndex Then
        dataMaxIndex = newUsedMaxIndex + 10
        ReDim Preserve data(0 To dataMaxIndex)
    End If

    ' 3. 合并数据
    Dim i As Long
    For i = 0 To othersMaxIndex
        usedMaxIndex = usedMaxIndex + 1
        data(usedMaxIndex) = others.Item(i)
    Next
End Sub

' 生成当前数组引用的切片
' @optional-param startIdx 起始位置
' @optional-param stopIdx 终止位置(最终不会包含 stop 的索引)
' @optional-param step 步长
'                      默认值 = 1
'                      不能是 0，如果设置了 0 将会抛出异常
Public Function IPrimitiveArrayRef_slice(Optional ByRef startIdx As Variant, Optional ByRef stopIdx As Variant, Optional ByRef step As Variant = 1) As IPrimitiveArrayRef
    ' 生成切片
    Dim result As PrimitiveArrayRefSlice
    Set result = New PrimitiveArrayRefSlice
    result.Init Me, startIdx, stopIdx, Step

    ' return
    Set IPrimitiveArrayRef_slice = result
End Function

' 克隆当前数组引用对象，并生成一个 `PrimitiveArrayRef` 类型的真实数组引用
Public Function IPrimitiveArrayRef_clone() As IPrimitiveArrayRef
    Dim result As PrimitiveArrayRef
    Set result = New PrimitiveArrayRef
    result.InitUnsafeForClone data, dataMaxIndex, usedMaxIndex

    Set IPrimitiveArrayRef_clone = result
End Function

' 复制索引 `[startIdx, endIdx)` 范围的内数据到新的数组引用对象中
' @param startIdx 起始索引
' @param endIdx 结束索引（不包括）
Public Function IPrimitiveArrayRef_copyToNewArray(Optional ByRef startIdx As Long = 0, Optional ByRef endIdx As Long = -1) As IPrimitiveArrayRef
    Dim result As PrimitiveArrayRef

    ' 1. 如果没有元素，或者起始索引超过了最大索引，则返回一个空对象
    If usedMaxIndex = -1 Or startIdx > usedMaxIndex Then
        Set result = New PrimitiveArrayRef
        result.Init 0
        Set IPrimitiveArrayRef_copyToNewArray = result
        Exit Function
    End If

    ' 2. 计算真实的索引
    Dim realStartIdx As Long
    ' 这里表示最后一个元素的索引位置
    Dim realLastIdx As Long
    If startIdx < 0 Then
        realStartIdx = 0
    Else
        realStartIdx = startIdx
    End If

    If endIdx < 0 Then
        realLastIdx = usedMaxIndex
    ElseIf endIdx > usedMaxIndex Then
        realLastIdx = usedMaxIndex
    Else 
        realLastIdx = endIdx - 1
    End If

    ' 快速失败
    ' 如果起始索引 > 最后一个元素的索引，则返回一个空结果
    If realStartIdx > realLastIdx Then
        Set result = New PrimitiveArrayRef
        result.Init 0
        Set IPrimitiveArrayRef_copyToNewArray = result
        Exit Function
    End If

    ' 3. 拷贝数据
    Set result = New PrimitiveArrayRef
    result.Init (realLastIdx - realStartIdx + 1)
    Set IPrimitiveArrayRef_copyToNewArray = result

    Dim i As Long
    For i = realStartIdx To realLastIdx
        IPrimitiveArrayRef_copyToNewArray.Add data(i)
    Next

End Function

' 获取当前数组引用中，数组缓存的真实长度
Public Function IPrimitiveArrayRef_unsafeRealSize() As Long
    IPrimitiveArrayRef_unsafeRealSize = dataMaxIndex + 1
End Function

' ==================================================
' 粘贴数据到 sheet
' ==================================================
' --------------------------------------------------
' 按行粘贴
' --------------------------------------------------
' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整行的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向右写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startCol 拷贝的起始列号（数值型） 或者 列名（字符串型）
Public Sub IPrimitiveArrayRef_pasteToSheetAsRow( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startCol As Variant, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRow: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRow: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRow: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3 将 startCol 转换成真实的列号
    Dim realStartColNo As Integer
    realStartColNo = UtilsWorksheet.convertVariantColToColNo(startCol)

    ' 2. 写出数据
    doPasteToSheetAsRow sh, startRowNo, realStartColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整行的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向右写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startColNo 拷贝的起始列号（数值型） 或者 列名（字符串型）
Public Sub IPrimitiveArrayRef_pasteToSheetAsRowByColNo( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColNo As Integer, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColNo: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColNo: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColNo: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3  如果 startColNo 超过了安全范围，则抛出异常
    If startColNo <= 0 Then
        Err.Raise 59805, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColNo: param[startColNo] must not be less than or equal to 0 And greater than UtilsWorksheet.MAX_COL_NO. Now startColNo= " & startColNo
    ElseIf startColNo > UtilsWorksheet.MAX_COL_NO Then
        Err.Raise 59805, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColNo: param[startColNo] must not be less than or equal to 0 And greater than UtilsWorksheet.MAX_COL_NO. Now startColNo= " & startColNo
    End If

    ' 2. 写出数据
    doPasteToSheetAsRow sh, startRowNo, startColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整行的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向右写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startColName 拷贝的起始列名
Public Sub IPrimitiveArrayRef_pasteToSheetAsRowByColName( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColName As String, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColName: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常，异常码 `59809`
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColName: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColName: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3 将 startColName 转换成真实的列号
    Dim realStartColNo As Integer
    realStartColNo = UtilsWorksheet.colStr2No(startColName)
    If realStartColNo <= 0 Then
        Err.Raise 59806, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColName: param[startColName] must not be less than to A And greater than UtilsWorksheet.MAX_COL_STR. Now startColName= " & startColName
    ElseIf startColNo > UtilsWorksheet.MAX_COL_NO Then
        Err.Raise 59806, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsRowByColName: param[startColName] must not be less than to A And greater than UtilsWorksheet.MAX_COL_STR. Now startColName= " & startColName
    End If

    ' 2. 写出数据
    doPasteToSheetAsRow sh, startRowNo, realStartColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整行的形式拷贝到各个单元格中
Private Sub doPasteToSheetAsRow( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColNo As Integer, _
    ByRef startIndex As Long, ByRef endIndex As Variant, _
    ByRef step As Long)

    ' 1. 计算需要的数据的范围
    Dim arrRange As Variant
    arrRange = UtilsArray.computeArraySliceInfo(usedMaxIndex + 1, startIndex, endIndex, step)

    Dim dataStart As Long
    Dim dataStep As Long
    Dim dataCount As Long
    dataStart = arrRange(0)
    dataStep = arrRange(2)
    dataCount = arrRange(3)

    ' 2. 生成写入时的最大列号
    ' 2.1 计算写入时的最大列号
    Dim endColNo As Integer
    endColNo = startColNo + usedMaxIndex

    ' 2.2 如果最大列号已经超过了列号的最大有效值，则重置
    If endColNo > UtilsWorksheet.MAX_COL_NO Then
        endColNo = UtilsWorksheet.MAX_COL_NO

        dataCount = endColNo - startColNo + 1
    End If

    ' 3. 写出数据
    If dataStart = 0 And dataStep = 1 And dataCount <= (usedMaxIndex + 1) Then
        ' 3.1 检查，如果是从索引 0 开始，输出所有数据，则直接写入
        With sh
            .Range(.Cells(startRowNo, startColNo), .Cells(startRowNo, endColNo)) = data
        End With
    Else
        ' 3.2 否则，一边计算位置一边写出数据
        Dim i As Long

        With sh
            For i = 0 To dataCount - 1
                .Cells(startRowNo, startColNo + i) = data(dataStart + i * dataStep)
            Next
        End With
    End If
End Sub

' --------------------------------------------------
' 按列粘贴
' --------------------------------------------------
' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整列的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向下写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startCol 拷贝的起始列号（数值型） 或者 列名（字符串型）
Public Sub IPrimitiveArrayRef_pasteToSheetAsCol( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startCol As Variant, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsCol: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常，异常码 `59809`
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsCol: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsCol: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3 将 startCol 转换成真实的列号
    Dim realStartColNo As Integer
    realStartColNo = UtilsWorksheet.convertVariantColToColNo(startCol)

    ' 2. 写入数据
    doPasteToSheetAsRow sh, startRowNo, realStartColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整列的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向下写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startColNo 拷贝的起始列号
Public Sub IPrimitiveArrayRef_pasteToSheetAsColByColNo( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColNo As Integer, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColNo: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常，异常码 `59809`
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColNo: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColNo: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3  如果 startColNo 超过了安全范围，则抛出异常
    If startColNo <= 0 Then
        Err.Raise 59805, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColNo: param[startColNo] must not be less than or equal to 0 And greater than UtilsWorksheet.MAX_COL_NO. Now startColNo= " & startColNo
    ElseIf startColNo > UtilsWorksheet.MAX_COL_NO Then
        Err.Raise 59805, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColNo: param[startColNo] must not be less than or equal to 0 And greater than UtilsWorksheet.MAX_COL_NO. Now startColNo= " & startColNo
    End If

    ' 2. 写入数据
    doPasteToSheetAsCol sh, startRowNo, startColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整列的形式拷贝到各个单元格中
' 写入方向：从指定位置开始，向下写入数据
'
' @param sh sheet 对象
' @param startRowNo 拷贝的起始行号
' @param startColName 拷贝的起始列名
Public Sub IPrimitiveArrayRef_pasteToSheetAsColByColName( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColName As String, _
    Optional ByRef startIndex As Long = 0, Optional ByRef endIndex As Variant, _
    Optional ByRef step As Long = 1)

    ' 1. 检查参数
    ' 1.1 如果 `sh == Nothing`，则抛出异常
    If sh Is Nothing Then
        Err.Raise 59803, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColName: Worksheet param[sh] is Nothing"
    End If

    ' 1.2 如果 startRowNo 超过了安全范围，则抛出异常，异常码 `59809`
    If startRowNo <= 0 Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColName: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    ElseIf startRowNo > UtilsWorksheet.MAX_ROW_NO Then
        Err.Raise 59809, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColName: param[startRowNo] must not be less than to 0 And greater than UtilsWorksheet.MAX_ROW_NO. Now startRowNo=" & startRowNo
    End If

    ' 1.3 将 startColName 转换成真实的列号
    Dim realStartColNo As Integer
    realStartColNo = UtilsWorksheet.colStr2No(startColName)
    If realStartColNo <= 0 Then
        Err.Raise 59806, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColName: param[startColName] must not be less than to A And greater than UtilsWorksheet.MAX_COL_STR. Now startColName= " & startColName
    ElseIf startColNo > UtilsWorksheet.MAX_COL_NO Then
        Err.Raise 59806, Description:="PrimitiveArrayRef.IPrimitiveArrayRef_pasteToSheetAsColByColName: param[startColName] must not be less than to A And greater than UtilsWorksheet.MAX_COL_STR. Now startColName= " & startColName
    End If

    ' 2. 写入数据
    doPasteToSheetAsCol sh, startRowNo, realStartColNo, _
                        startIndex, endIndex, step
End Sub

' 从指定 sheet 的指定位置开始，将当前数组引用中的数据以整列的形式拷贝到各个单元格中
Private Sub doPasteToSheetAsCol( _
    ByRef sh As Worksheet, ByRef startRowNo As Long, ByRef startColNo As Integer, _
    ByRef startIndex As Long, ByRef endIndex As Variant, _
    ByRef step As Long)

    ' 1. 计算需要的数据的范围
    Dim arrRange As Variant
    arrRange = UtilsArray.computeArraySliceInfo(usedMaxIndex + 1, startIdx, endIndex, step)

    Dim dataStart As Long
    Dim dataStep As Long
    Dim dataCount As Long
    dataStart = arrRange(0)
    dataStep = arrRange(2)
    dataCount = arrRange(3)

    ' 2. 生成写入时的最大列号
    ' 2.1 计算写入时的最大列号
    Dim endRowNo As Integer
    endRowNo = startRowNo + usedMaxIndex

    ' 2.2 如果最大列号已经超过了列号的最大有效值，则重置
    If endRowNo > UtilsWorksheet.MAX_COL_STR Then
        endRowNo = UtilsWorksheet.MAX_COL_STR

        dataCount = endRowNo - startRowNo + 1
    End If

    ' 3. 写出数据
    Dim i As Long
    With sh
        For i = 0 To dataCount - 1
            .Cells(startRowNo + i, startColNo) = data(dataStart + i * dataStep)
        Next
    End With
End Sub